<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the Session class.
 *
 * PHP version 5
 *
 * This file is part of Majic - A rapid-development web application framework
 * for PHP 5.
 *
 * Majic is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Majic is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Majic. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     Session
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007-2008 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: Session.php 41 2008-08-30 16:11:11Z mjijackson $
 */

// disable automatic session starting
ini_set('session.auto_start', 0);

/**
 * Automatically validates sessions and provides convenient accessor methods for
 * the superglobal $_SESSION array.
 *
 * @package     Session
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class Session
{

    /**
     * The store that is currently being used to store the session.
     *
     * @var     Session_Store
     * @access  private
     * @static
     */
    private static $store;

    /**
     * Sets the store that will be used for session data. Should be called
     * before {@link start() start}.
     *
     * @param   Session_Store   $store      The session store object
     * @return  void
     * @access  public
     * @static
     */
    public static function setStore(Session_Store $store)
    {
        self::$store = $store;

        // set up the custom save handler
        $store->set();
    }

    /**
     * Starts the session.
     *
     * @param   string  $name       (optional) The session name to use
     * @return  void
     * @access  public
     * @static
     */
    public static function start($name = null)
    {
        if ($name) {
            session_name($name);
        }

        session_start();

        if (!isset($_SESSION['__SESSION_IS_NEW'])) {
            $_SESSION['__SESSION_IS_NEW'] = true;
        } else {
            $_SESSION['__SESSION_IS_NEW'] = false;
        }
    }

    /**
     * Destroys the current session.
     *
     * @param   bool    $expire_cookie      True to expire the session cookie
     *                                      as well, defaults to true
     * @return  void
     * @access  public
     */
    public static function destroy($expire_cookie = true)
    {
        $_SESSION = array();

        if ($expire_cookie) {
            self::expireCookie();
        }

        session_destroy();

        if (self::$store) {
            // need to reset custom save handler
            // see http://bugs.php.net/bug.php?id=32330
            self::$store->set();
        }
    }

    /**
     * Regenerates the session id.
     *
     * @param   bool    $delete_old     True to delete the old session store
     * @return  bool                    True on success, false on failure
     * @access  public
     * @static
     */
    public static function regenerateId($delete_old = false)
    {
        return session_regenerate_id($delete_old);
    }

    /**
     * Regenerates the session id on the given interval.
     *
     * @param   int     $interval       The interval at which the session id
     *                                  should be regenerated (in seconds)
     * @param   bool    $delete_old     True to delete the old session store
     * @return  bool                    True on success, false on failure
     * @access  public
     * @static
     */
    public static function regenerateIdInterval($interval, $delete_old = false)
    {
        $ret = true;
        $interval = max(0, $interval);

        $gen = time();

        if (isset($_SESSION['__SESSION_ID_GENERATED'])) {
            if ($_SESSION['__SESSION_ID_GENERATED'] + $interval < time()) {
                $ret = self::regenerateId($delete_old);
            } else {
                // recycle old generation time for accuracy
                $gen = $_SESSION['__SESSION_ID_GENERATED'];
            }
        }

        // set/update id generation time
        $_SESSION['__SESSION_ID_GENERATED'] = $gen;

        return $ret;
    }

    /**
     * Expires the session cookie.
     *
     * @return  void
     * @access  public
     */
    public static function expireCookie()
    {
        if (isset($_COOKIE[session_name()])) {
            $p = session_get_cookie_params();
            setcookie(session_name(), false, 1, $p['path'], $p['domain'], $p['secure']);
        }
    }

    /**
     * Determines whether or not this session is new. This means that the
     * session was created on this request.
     *
     * @return  bool            True if the session is new, false otherwise
     * @access  public
     * @static
     */
    public static function isNew()
    {
        // The best way to check if a session is actually new is to look for
        // an existing session store with the given id. However, this is not
        // possible with the default PHP "files" save handler, so it is
        // emulated here.
        return !isset($_SESSION['__SESSION_IS_NEW']) || $_SESSION['__SESSION_IS_NEW'];
    }

    /**
     * Sets the duration of the session.
     *
     * @param   int     $time       The duration of the session (in seconds)
     * @param   bool    $add        True to add the given time value to the
     *                              current duration, false otherwise
     * @access  public
     * @static
     */
    public static function setDuration($time, $add = false)
    {
        if ($add && isset($_SESSION['__SESSION_DURATION'])) {
            $_SESSION['__SESSION_DURATION'] += $time;
        } else {
            $_SESSION['__SESSION_DURATION'] = $time;
        }

        // set timestamp for the first time duration is set
        if (!isset($_SESSION['__SESSION_EXPIRE_TS'])) {
            $_SESSION['__SESSION_EXPIRE_TS'] = time();
        }
    }

    /**
     * Determines whether or not the current session is expired.
     *
     * @return  bool            True if the session is expired, false otherwise
     * @access  public
     * @static
     */
    public static function isExpired()
    {
        return self::timeUntilExpired() <= 0;
    }

    /**
     * Gets the amount of time (in seconds) remaining until the session is
     * expired. Will return 1 if no session {@link setDuration() duration} has
     * been set.
     *
     * @return  int             The time until the session is expired
     * @access  public
     * @static
     */
    public static function timeUntilExpired()
    {
        if (isset($_SESSION['__SESSION_DURATION']) && isset($_SESSION['__SESSION_EXPIRE_TS'])) {
            return ($_SESSION['__SESSION_EXPIRE_TS'] + $_SESSION['__SESSION_DURATION']) - time();
        }

        return 1;
    }

    /**
     * Sets the idle time of the session.
     *
     * @param   int     $time       The idle time of the session (in seconds)
     * @param   bool    $add        True to add the given time value to the
     *                              current idle time, false otherwise
     * @access  public
     * @static
     */
    public static function setIdle($time, $add = false)
    {
        if ($add && isset($_SESSION['__SESSION_IDLE'])) {
            $_SESSION['__SESSION_IDLE'] += $time;
        } else {
            $_SESSION['__SESSION_IDLE'] = $time;
        }

        // set timestamp for first time idle is set
        if (!isset($_SESSION['__SESSION_IDLE_TS'])) {
            $_SESSION['__SESSION_IDLE_TS'] = time();
        }
    }

    /**
     * Determines whether or not the current session is idle (has exceeded its
     * predetermined {@link setIdle() idle} time).
     *
     * @return  bool            True if the session is idle, false otherwise
     * @access  public
     * @static
     */
    public static function isIdle()
    {
        return self::timeUntilIdle() <= 0;
    }

    /**
     * Gets the amount of time (in seconds) remaining until the session is
     * considered idle. Will return 1 if no {@link setIdle() idle time} limit
     * has been set.
     *
     * @return  int             The time until the session is idle
     * @access  public
     * @static
     */
    public static function timeUntilIdle()
    {
        if (isset($_SESSION['__SESSION_IDLE']) && isset($_SESSION['__SESSION_IDLE_TS'])) {
            return ($_SESSION['__SESSION_IDLE_TS'] + $_SESSION['__SESSION_IDLE']) - time();
        }

        return 1;
    }

    /**
     * Updates the session's idle timestamp to the current time. Should be
     * called on every request after idle session logic is enforced.
     *
     * @return  void
     * @access  public
     * @static
     */
    public static function updateIdle()
    {
        if (isset($_SESSION['__SESSION_IDLE_TS'])) {
            $_SESSION['__SESSION_IDLE_TS'] = time();
        }
    }

}

?>
